package com.jokerpan.project.service.impl;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.CopyObjectRequest;
import com.aliyun.oss.model.CopyObjectResult;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.jokerpan.project.config.AliyunOssConfig;
import com.jokerpan.project.entity.BackFiles;
import com.jokerpan.project.entity.Users;
import com.jokerpan.project.service.AliyunOssService;
import com.jokerpan.project.service.BackFilesService;
import com.jokerpan.project.service.UsersService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author jokerpan
 */
@Service("aliyunOssService")
public class AliyunOssServiceImpl implements AliyunOssService {
    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Resource
    AliyunOssConfig aliyunOssConfig;
    @Resource
    BackFilesService backFilesService;
    @Resource
    UsersService usersService;

    @Override
    public String uploadFile(String table, Integer id, MultipartFile file) {
        String url;
        if (table.equals("USERS")){
            BackFiles temp = new BackFiles();
            temp.setSource(table);
            temp.setPk(id);
            BackFiles back = usersService.queryAvatar(temp);
            if (back != null){
                if (!deleteFile(back.getUrl()) || !backFilesService.deleteBySourceAndPk(back)){
                    return "error";
                }
            }
        }
        // 获取文件保存路径
        String path = getFilePath(table, id, file);
        if (push(path, file)){
            url = String.format("https://%s.%s/%s", aliyunOssConfig.getBucketName(), aliyunOssConfig.getEndpoint(), path);
            BackFiles backFiles = new BackFiles();
            backFiles.setSource(table);
            backFiles.setPk(id);
            backFiles.setUrl(url);
            if(backFilesService.add(backFiles)){
                if (table.equals("USERS")){
                    Users users = new Users();
                    users.setId(id);
                    users.setAvatar(url);
                    usersService.update(users);
                }
                return url;
            }else {
                return "error";
            }
        }else {
            return "error";
        }
    }

    @Override
    public String uploadTemporaryFile(Integer id, MultipartFile file) {
        String url;
        // 获取文件保存路径
        String path = getTemporaryFilePath(id, file);
        if (push(path, file)){
            url = String.format("https://%s.%s/%s", aliyunOssConfig.getBucketName(), aliyunOssConfig.getEndpoint(), path);
            BackFiles backFiles = new BackFiles();
            backFiles.setSource("Temporary");
            backFiles.setPk(id);
            backFiles.setUrl(url);
            if(backFilesService.add(backFiles)){
                return url;
            }else {
                return "error";
            }
        }else {
            return "error";
        }
    }

    @Override
    public boolean deleteFile(String path) {
        boolean ok = true;
        if (path.indexOf(".com") > -1){
            path = path.split("com")[1].substring(1);
        }
        Long startTime = System.currentTimeMillis();
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(aliyunOssConfig.getEndpoint(), aliyunOssConfig.getAccessKeyId(), aliyunOssConfig.getAccessKeySecret());
        try {
            // 判断文件是否存在
            boolean found = ossClient.doesObjectExist(aliyunOssConfig.getBucketName(), path);
            if (found) {
                // 删除附件
                ossClient.deleteObject(aliyunOssConfig.getBucketName(), path);
                Long endTime = System.currentTimeMillis();
                log.info("删除成功：共1文件。总耗时：" + (endTime - startTime) + "ms");
            }
        } catch (Exception e) {
            ok = false;
            e.printStackTrace();
        }
        // 关闭OSSClient。
        ossClient.shutdown();

        return ok;
    }

    /**
     * 判断文件是否存在
     * @param objectName 文件路径
     * @return 是否存在
     */
    public boolean hasFile(String objectName) {
        // 创建OSSClient实例
        OSS ossClient = new OSSClientBuilder().build(aliyunOssConfig.getEndpoint(), aliyunOssConfig.getAccessKeyId(), aliyunOssConfig.getAccessKeySecret());
        try {
            // 判断文件是否存在。如果返回值为true，则文件存在，否则存储空间或者文件不存在。
            return ossClient.doesObjectExist(aliyunOssConfig.getBucketName(), objectName);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }

    /**
     * 推送
     * @param path
     * @param file
     * @return 是否成功
     */
    private boolean push(String path, MultipartFile file) {
        boolean ok = true;

        // 先将内容保存到字节数组，然后推送到OSS。如果推送失败，则尝试重新推送
        try {
            byte[] fileBytes = file.getBytes();
            int loop = 2;
            while (!saveToOss(path, fileBytes)) {
                // 推送到OSS,如果失败尝试重新推送 3 次
                if (loop == 0) {
                    ok = false;
                    break;
                }
                loop--;
            }
        } catch (IOException e) {
            ok = false;
            e.printStackTrace();
        }

        return ok;
    }

    /**
     * 保存至aliyun Oss
     * @param path
     * @param fileBytes
     * @return
     */
    public boolean saveToOss(String path, byte[] fileBytes) {
        boolean ok = true;

        if (path.startsWith("/")) {
            path = path.substring(1);
        }
        OSS ossClient = new OSSClientBuilder().build(aliyunOssConfig.getEndpoint(), aliyunOssConfig.getAccessKeyId(), aliyunOssConfig.getAccessKeySecret());

        try {
            // 创建PutObjectRequest对象。
            // 填写Bucket名称和Object完整路径。Object完整路径中不能包含Bucket名称。
            PutObjectRequest putObjectRequest = new PutObjectRequest(aliyunOssConfig.getBucketName(), path, new ByteArrayInputStream(fileBytes));

            PutObjectResult res = ossClient.putObject(putObjectRequest);
        } catch (Exception e) {
            ok = false;
            e.printStackTrace();
        }

        ossClient.shutdown();

        return ok;
    }

    /**
     * 拷贝文件并删除旧文件
     */
    @Override
    public String copyFile(String table, Integer pk, String oldPath){
        OSS ossClient = new OSSClientBuilder().build(aliyunOssConfig.getEndpoint(), aliyunOssConfig.getAccessKeyId(), aliyunOssConfig.getAccessKeySecret());
        String newPath = getNewFilePath(table, pk, oldPath);
        oldPath = oldPath.substring(oldPath.lastIndexOf("com") + 4);
        try {
            //oldPath：oss源文件的地址
            //newPath：oss目标文件的地址
            CopyObjectResult copyObjectResult = ossClient.copyObject(aliyunOssConfig.getBucketName(), oldPath, aliyunOssConfig.getBucketName(), newPath);
            this.deleteFile(oldPath);
            // 关闭OSSClient。
            ossClient.shutdown();
        } catch (Exception ex) {
            newPath = "error";
            log.info("拷贝文件时发生错误。。。");
        }
        return newPath.equals("error") ? newPath : String.format("https://%s.%s/%s", aliyunOssConfig.getBucketName(), aliyunOssConfig.getEndpoint(), newPath);
    }

    /**
     * 构造上传附件路径
     * @param sourceTableName 关联表名
     * @param id              关联ID
     * @param file            附件
     * @return 上传附件路径
     */
    public String getFilePath(String sourceTableName, Integer id, MultipartFile file) {

        String re = "";

        // 文件的扩展名
        String extensionName = file.getOriginalFilename().split("\\.")[1].toLowerCase();
        // 随机数值转换36进制字符串区分唯一附件名，避免同一毫秒多次调用导致附件名重复
        long random = Long.parseLong((Math.random() + "").replace("0.", ""));
        String fileName = System.currentTimeMillis() + "_" + Long.toString(random, 36) + "." + extensionName;
        String mediaType = isImage(file) ? "pic" : "video";
        String source = sourceTableName.toUpperCase();
        //日期字符串数组
        String[] dateStrArry = getDayStr().split("-");
        re = String.format(source + "/%s/%s/%s/%s/%s", mediaType, id, dateStrArry[0] + dateStrArry[1], dateStrArry[2], fileName);
        return re;
    }

    /**
     * 构造临时上传附件路径
     * @return 上传附件路径
     */
    public String getTemporaryFilePath(Integer id,MultipartFile file) {

        String re = "";
        // 文件的扩展名
        String extensionName = file.getOriginalFilename().split("\\.")[1].toLowerCase();
        long random = Long.parseLong((Math.random() + "").replace("0.", ""));
        String fileName = System.currentTimeMillis() + "_" + Long.toString(random, 36) + "." + extensionName;
        String mediaType = isImage(file) ? "pic" : "video";
        String source = "Temporary";
        //日期字符串数组
        String[] dateStrArry = getDayStr().split("-");
        re = String.format(source + "/%s/%s/%s/%s/%s", mediaType, id, dateStrArry[0] + dateStrArry[1], dateStrArry[2], fileName);
        return re;
    }

    /**
     * 根据旧地址构造新路径
     * @return 上传附件路径
     */
    public String getNewFilePath(String table, Integer pk, String oldPath) {
        String re = "";
        String fileName = oldPath.substring(oldPath.lastIndexOf("/") + 1);
        String extensionName = fileName.split("\\.")[1].toLowerCase();
        long random = Long.parseLong((Math.random() + "").replace("0.", ""));
        String newFileName = System.currentTimeMillis() + "_" + Long.toString(random, 36) + "." + extensionName;
        String mediaType = extensionName.indexOf("jpg") > -1 ? "image" : "other";
        String source = table.toUpperCase();
        //日期字符串数组
        String[] dateStrArry = getDayStr().split("-");
        re = String.format(source + "/%s/%s/%s/%s/%s", mediaType, pk, dateStrArry[0] + dateStrArry[1], dateStrArry[2], newFileName);
        return re;
    }

    /**
     * 判断是否为图片
     * @param file 附件
     * @return boolean
     */
    public boolean isImage(MultipartFile file) {
        // 文件类型
        String fileContentType = file.getContentType().toUpperCase();
        return fileContentType.contains("IMAGE");
    }

    /**
     * 格式化日期
     * @return 格式化日期字符串
     */
    public String getDayStr() {
        // 格式化时间
        SimpleDateFormat sdf = new SimpleDateFormat();
        sdf.applyPattern("yyyy-MM-dd");
        // 获取当前时间
        Date date = new Date();
        return sdf.format(date);
    }
}
